﻿using System;
using Dapper;
using ZeroMQ;
using System.Text;
using System.Data;
using System.Linq;
using System.Xml.Linq;
using System.Configuration;
using System.Data.SqlClient;
using Web.Auction.Bid.Models;
using System.Collections.Generic;
using Web.Auction.Bid.Extensions;
using System.Collections.Concurrent;

namespace Web.Auction.Bid
{
    public class AuctionManager
    {
        public static readonly AuctionManager Instance = new AuctionManager();

        private readonly string openTime;
        private readonly string closeTime;
        private readonly string auctionConnectionString;
        private readonly string paymentConnectionString;

        private volatile bool updatingStatus;
        private readonly object marketStateLock = new object();

        private readonly object updateStatusLock = new object();
        private volatile MarketState marketState = MarketState.Closed;

        private readonly Random random;
        private readonly IList<Models.Bid> bids;
        private readonly ConcurrentDictionary<int, Models.Auction> dictionary;

        public DateTime OpenTime
        {
            get { return DateTime.Parse(openTime); }
        }

        public DateTime CloseTime
        {
            get { return DateTime.Parse(closeTime); }
        }

        public MarketState MarketState
        {
            get { return marketState; }
            private set { marketState = value; }
        }

        public IList<Models.Auction> Auctions
        {
            get { return dictionary.Values.ToList(); }
        }

        private AuctionManager()
        {
            random = new Random();

            openTime = ConfigurationManager.AppSettings["openTime"] ?? "05:00:00";
            closeTime = ConfigurationManager.AppSettings["closeTime"] ?? "23:59:59";
            auctionConnectionString = ConfigurationManager.ConnectionStrings["SilenthinkAuction"].ConnectionString;
            paymentConnectionString = ConfigurationManager.ConnectionStrings["SilenthinkPayment"].ConnectionString;

            const int initialCapacity = 101;
            var numProcs = Environment.ProcessorCount;
            var concurrencyLevel = numProcs * 2;
            bids = new List<Models.Bid>(initialCapacity);
            dictionary = new ConcurrentDictionary<int, Models.Auction>(concurrencyLevel, initialCapacity);
        }

        public bool CheckTime(DateTime time)
        {
            return (OpenTime <= time && time <= CloseTime);
        }

        public void OpenMarket()
        {
            lock (marketStateLock)
            {
                if (MarketState == MarketState.Open) return;
                SetDictionary();
                //LoadDefaultTransfiniteUsers();
                MarketState = MarketState.Open;
            }
        }

        public void CloseMarket()
        {
            lock (marketStateLock)
            {
                if (MarketState != MarketState.Open) return;
                ResetDictionary();
                MarketState = MarketState.Closed;
            }
        }

        public string GetData()
        {
            var now = DateTime.Now;
            var result = new SkResult<object>(false, "更新状态失败！") { Extra = "statusChanged" };
            if (CheckTime(now))
            {
                lock (updateStatusLock)
                {
                    if (!updatingStatus)
                    {
                        AutoBid();
                        updatingStatus = true;
                        var temp = new List<Models.Auction>();//倒计时-1
                        var finished = new List<Models.Auction>(); //成交的
                        var suspended = new List<Models.Auction>(); //暂停的
                        var array = Auctions.Where(auction => now >= auction.StartDateTime && auction.UserId != 0 && auction.Status == AuctionStatus.Normal).ToList();
                        foreach (var auction in array)
                        {
                            if (now >= auction.StopDateTime)
                            {
                                if (auction.ExpectedTime != null && now > auction.ExpectedTime.Value)
                                {
                                    auction.Status = AuctionStatus.Finished;
                                    auction.TransactionTime = now;
                                    auction.OrderId = now.ToString("CyyyyMMddHHmmssfffffff") + auction.UserId;
                                    finished.Add(auction);
                                }
                                else
                                {

                                    var second = auction.Interval;
                                    var expectedTime = auction.StartDateTime.AddSeconds(second);

                                    auction.ExpectedTime = expectedTime;
                                    auction.Status = AuctionStatus.Suspended;
                                    suspended.Add(auction);
                                }
                            }
                            else
                            {
                                if (auction.ExpectedTime != null && now >= auction.ExpectedTime.Value)
                                {
                                    auction.Status = AuctionStatus.Finished;
                                    auction.TransactionTime = now;
                                    auction.OrderId = now.ToString("CyyyyMMddHHmmssfffffff") + auction.UserId;

                                    finished.Add(auction);
                                }
                                else
                                {
                                    var counter = auction.Counter - 1;
                                    auction.Counter = counter < 0 ? 0 : counter;
                                    temp.Add(auction);
                                }
                            }
                        }
                        var auctions = suspended.Concat(finished).ToList();
                        if (UpdateToDatabase(auctions))
                        {
                            if (bids.Count > 0)
                            {
                                bids.Clear();
                            }
                            var a = temp.Select(o => new { o.Id, Interval = o.Counter });
                            var b = suspended.Select(o => o.Id);
                            var c = finished.Select(o => o.Id);
                            var data = new { A = a, B = b, C = c };
                            result.Status = true;
                            result.Data = data;
                        }
                        updatingStatus = false;
                    }
                }
            }
            return result.ToJson();
        }

        public SkResult<object> Bid(int auctionId, int userId, string username, string nickname, string ipaddess)
        {
            var now = DateTime.Now;

            var result = new SkResult<object>(false);

            var auction = dictionary[auctionId];

            if (auction == null)
            {
                result.Message = "参数错误！";
                return result;
            }
            if (now < auction.StartDateTime || now > auction.StopDateTime)
            {
                result.Message = "不在竞拍时间内！";
                return result;
            }
            if (auction.Status == AuctionStatus.Finished)
            {
                result.Message = "该拍品已成交！";
                return result;
            }
            if (userId == auction.UserId)
            {
                result.Message = "不可连续出价！";
                return result;
            }
            //var status = TransfiniteUsers.FirstOrDefault(o => o.Key == auction.Grade).Value.Contains(userId);
            //if (status)
            //{
            //    result.Message = "您已超限！";
            //    return result;
            //}
            //if(auction.ZeroSupported)
            //{
            //    //0元竞购
            //}
            //else
            //{
            //    if(auction.FreeBidSupported)
            //    {
            //        if(auction.FreeBidLimitation==FreeBidLimitation.Unlimited)
            //        {
            //            //先消耗免费拍币再消耗付费拍币
            //        }
            //        else if(auction.FreeBidLimitation==FreeBidLimitation.NTimesOnly)
            //        {
            //            if(auction.FreeBidCount>auction.FreeBidLimitationValue)
            //            {
            //                result.Message = "您使用已超限！";
            //            }
            //        }
            //    }
            //    else
            //    {
            //        //使用付费拍币
            //    }
            //}
            lock (updateStatusLock)
            {
                if (!updatingStatus)
                {
                    string message;
                    updatingStatus = true;
                    if (UpdateUserBidCurrency(userId, auction.PerPrice, auction.FreeBidSupported, out message))
                    {
                        var bid = new Models.Bid();

                        var price = auction.BidPrice + 0.01M;
                        var second = auction.Interval;
                        var expectedTime = now.AddSeconds(second);
                        auction.Counter = second;
                        auction.BidPrice = price;
                        auction.UserId = userId;
                        auction.NickName = nickname;
                        auction.UserName = username;
                        auction.IpAddress = ipaddess;
                        auction.ExpectedTime = expectedTime;

                        bid.PerPrice = auction.PerPrice;
                        bid.AuctionId = auction.Id;
                        bid.BidPrice = price;
                        bid.UserId = auction.UserId;
                        bid.NickName = auction.NickName;
                        bid.IpAddress = ipaddess;
                        bid.CreatedBy = username;
                        bid.CreatedOn = now;

                        bids.Add(bid);

                        result.Status = true;
                        result.Extra = "priceChanged";
                        result.Data = new { Id = auctionId, UserId = userId, NickName = nickname, Price = auction.BidPrice.ToString("N"), auction.Interval, Ip = ipaddess };
                    }
                    else
                    {
                        result.Message = message;
                    }
                    updatingStatus = false;
                }
            }
            if (result.Status)
            {
                var message = result.ToJson();
                MvcApplication.Socket.Send(message, Encoding.UTF8);
            }
            return result;
        }

        private void SetDictionary()
        {
            var auctions = GetAuctions();

            foreach (var auction in auctions)
            {
                dictionary.TryAdd(auction.Id, auction);
            }
        }

        private void ResetDictionary()
        {
            lock (updateStatusLock)
            {
                if (updatingStatus) return;
                updatingStatus = true;

                var auctions = Auctions.Where(o => o.Status != AuctionStatus.Finished).ToList();
                if (UpdateToDatabase(auctions, true))
                {
                    bids.Clear();
                    dictionary.Clear();
                }
                updatingStatus = false;
            }
        }

        public IList<Models.Auction> GetClosedAuctions()
        {
            return GetAuctions(0);
        }

        public IList<Models.Auction> GetTodayAuctions()
        {
            return GetAuctions();
        }

        public IList<Models.Auction> GetTomorrowAuctions()
        {
            return GetAuctions(2);
        }

        public void AutoBid()
        {
            List<Robot> robots;
            var time = DateTime.Now;
            using (var connection = new SqlConnection(auctionConnectionString).EnsureOpen())
            {
                const string sql = @"select * from [Auction.Robot] where [status]=0 or [status]=1";
                robots = connection.Query<Robot>(sql).ToList();
            }

            if (robots.Count < 1) return;
            foreach (var robot in robots)
            {
                var auction = Auctions.FirstOrDefault(o => o.Id == robot.AuctionId);
                if (auction == null) continue;
                if (robot.Status == 1 && !robot.NextTimeOn.HasValue && auction.BidPrice >= robot.StartPrice)
                {
                    if (robot.Timing == 0)
                    {
                        robot.Status = 0; //自动启动机器人
                        var val = random.Next(5, auction.Interval);
                        robot.NextTimeOn = time.AddSeconds(val);
                    }
                    else if (robot.Timing == 1 && auction.Counter < 10)
                    {
                        robot.Status = 0; //自动启动机器人
                        var val = random.Next(15, auction.Interval);
                        robot.NextTimeOn = time.AddSeconds(val);
                    }
                }
                else if (auction.Status == AuctionStatus.Finished)
                {
                    robot.Status = (byte)(auction.UserId == robot.UserId ? 7 : 3);
                    robot.NextTimeOn = null;
                }
                else if (robot.Category == 1 && (robot.ExpirationOn.HasValue && robot.ExpirationOn.Value >= time))
                {
                    robot.Status = 5; //机器人到期
                    robot.NextTimeOn = null;
                }
                else if (robot.Status == 0 && auction.UserId != robot.UserId && robot.NextTimeOn.HasValue && robot.NextTimeOn.Value.Subtract(time).TotalSeconds < 2)
                {
                    var result = Bid(robot.AuctionId, robot.UserId, robot.CreatedBy, robot.NickName, robot.IpAddress);
                    if (result.Status)
                    {
                        var counter = robot.Counter;
                        robot.Counter = counter + 1;
                        if (robot.Counter == robot.Times)
                        {
                            robot.Status = 6; //已完成所设定出价次数
                            robot.NextTimeOn = null;
                        }
                        else
                        {
                            if (robot.Timing == 0)
                            {
                                var val = random.Next(5, auction.Interval);
                                robot.NextTimeOn = time.AddSeconds(val);
                            }
                            else if (robot.Timing == 1 && auction.Counter < 10)
                            {
                                var val = random.Next(15, auction.Interval);
                                robot.NextTimeOn = time.AddSeconds(val);
                            }
                        }
                    }
                    else
                    {
                        robot.Status = 4;
                        robot.Extra = result.Message + ";";
                        robot.NextTimeOn = null;
                    }
                }
            }
            var xElement = new XElement("robots", robots.Select(o => new XElement("robot",
                                                                               new XAttribute("id", o.Id),
                                                                               new XAttribute("counter", o.Counter),
                                                                               new XAttribute("nexttimeon", o.NextTimeOn.HasValue ? o.NextTimeOn.Value.ToString("yyyy-MM-dd HH:mm:ss") : "null"),
                                                                               new XAttribute("extra", o.Extra),
                                                                               new XAttribute("status", o.Status)
                                                                      )));
            var xml = xElement.ToString();

            //更新机器人状态到数据库
            try
            {
                using (var connection = new SqlConnection(auctionConnectionString).EnsureOpen())
                {
                    var param = new DynamicParameters();
                    param.Add("@xml", xml, DbType.Xml);
                    connection.Execute("[Proc.Auction.Robot.Update]", param, commandType: CommandType.StoredProcedure);
                }
            }
            catch (Exception)
            {
                MvcApplication.Logger.Warn(xml);
            }
        }

        private IList<Models.Auction> GetAuctions(byte type = 1)
        {
            var now = DateTime.Now;
            var builder = new SqlBuilder().Field("*,Interval as Counter").Table("[Auction]");

            switch (type)
            {
                case 0:
                    {
                        var time = new DateTime(now.Year, now.Month, now.Day).AddMinutes(-3);
                        builder.Where("[Status]=1 AND [TransactionTime] IS NOT NULL AND [TransactionTime]>@time", new { time });
                    }
                    break;
                case 1:
                    {
                        var time = new DateTime(now.Year, now.Month, now.Day).AddDays(1);
                        builder.Where("[Status]=0 AND [OnlineTime]<@time", new { time });
                    }
                    break;
                case 2:
                    {
                        var begin = new DateTime(now.Year, now.Month, now.Day).AddDays(1);
                        var end = new DateTime(now.Year, now.Month, now.Day).AddDays(2);
                        builder.Where("[Status]=0 AND [OnlineTime]>=@begin AND [OnlineTime]<@end", new { begin, end });
                    }
                    break;
            }
            var template = builder.AddTemplate(SqlTemplate.Select);
            using (var connection = new SqlConnection(auctionConnectionString).EnsureOpen())
            {
                return connection.Query<Models.Auction>(template).ToList();
            }
        }

        private bool UpdateUserBidCurrency(int userId, int amount, bool freeBidSupported, out string message)
        {
            message = string.Empty;
            try
            {
                var param = new DynamicParameters();
                param.AddDynamicParams(new { userId, amount, freeBidSupported });

                param.Add("status", 1, DbType.Int32, ParameterDirection.Output);
                param.Add("message", string.Empty, DbType.String, ParameterDirection.Output, 50);

                using (var connection = new SqlConnection(paymentConnectionString).EnsureOpen())
                {
                    connection.Execute("[Proc.User.Bid]", param, null, 3, CommandType.StoredProcedure);
                    message = param.Get<string>("message");
                    return param.Get<int>("status") == 0;
                }
            }
            catch (Exception e)
            {
                MvcApplication.Logger.Error("AuctionManager.UpdateUserBidCurrency：" + e.Message);
                return false;
            }
        }

        private bool UpdateToDatabase(IList<Models.Auction> auctions, bool marketClose = false)
        {
            var xElement = new XElement("xml");
            if (auctions.Count > 0)
            {
                if (marketClose)
                {
                    xElement.Add(new XElement("auctions", auctions.Select(o => new XElement("auction",
                                                                                            new XAttribute("id", o.Id),
                                                                                            new XAttribute("userid", o.UserId),
                                                                                            new XAttribute("nickname", o.NickName),
                                                                                            new XAttribute("username", o.UserName),
                                                                                            new XAttribute("bidprice", o.BidPrice.ToString("N")),
                                                                                            new XAttribute("ipaddress", o.IpAddress),
                                                                                            new XAttribute("orderid", ""),
                                                                                            new XAttribute("expectedtime", "null"),
                                                                                            new XAttribute("transactiontime", "null"),
                                                                                            new XAttribute("status", "0")
                                                                                   ))));
                }
                else
                {
                    xElement.Add(new XElement("auctions", auctions.Select(o => new XElement("auction",
                                                                                            new XAttribute("id", o.Id),
                                                                                            new XAttribute("userid", o.UserId),
                                                                                            new XAttribute("nickname", o.NickName),
                                                                                            new XAttribute("username", o.UserName),
                                                                                            new XAttribute("bidprice", o.BidPrice.ToString("N")),
                                                                                            new XAttribute("ipaddress", o.IpAddress),
                                                                                            new XAttribute("orderid", o.Status == AuctionStatus.Finished ? o.OrderId : ""),
                                                                                            new XAttribute("expectedtime", o.Status == AuctionStatus.Suspended ? (o.ExpectedTime.HasValue ? o.ExpectedTime.Value.ToString("yyyy-MM-dd HH:mm:ss:fff") : "null") : "null"),
                                                                                            new XAttribute("transactiontime", o.Status == AuctionStatus.Finished ? (o.TransactionTime.HasValue ? o.TransactionTime.Value.ToString("yyyy-MM-dd HH:mm:ss:fff") : "null") : "null"),
                                                                                         new XAttribute("status", o.Status == AuctionStatus.Suspended ? 0 : 1)
                                                                                   ))));
                }
            }
            if (bids.Count > 0)
            {
                xElement.Add(new XElement("bids", bids.Select(o => new XElement("bid",
                                                                                new XAttribute("auctionid", o.AuctionId),
                                                                                new XAttribute("perprice", o.PerPrice),
                                                                                new XAttribute("userid", o.UserId),
                                                                                new XAttribute("nickname", o.NickName),
                                                                                new XAttribute("bidprice", o.BidPrice.ToString("N")),
                                                                                new XAttribute("ipaddress", o.IpAddress),
                                                                                new XAttribute("createdby", o.CreatedBy),
                                                                                new XAttribute("createdon", o.CreatedOn.ToString("yyyy-MM-dd HH:mm:ss:fff"))
                                                                       ))));
            }
            if (!xElement.HasElements)
            {
                return true;
            }
            var xml = xElement.ToString();
            try
            {
                using (var connection = new SqlConnection(auctionConnectionString).EnsureOpen())
                {
                    connection.Execute("[Proc.Auction.Interval]", new { xml }, null, 3, CommandType.StoredProcedure);
                }
                return true;
            }
            catch (Exception e)
            {
                xElement.Save(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.bin"));
                MvcApplication.Logger.Error("AuctionManager.UpdateToDatabase：" + e.Message);
                return false;
            }
        }
    }
}